package graph.util;

import graph.editor.GraphComponent;

import java.awt.Component;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.ArrayList;
import java.util.List;
import java.util.StringTokenizer;

import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import javax.swing.JTextField;
import javax.swing.ScrollPaneConstants;

import observable.ObservableAutomaton;
import automaton.NotDeterministicInitialStateException;
import automaton.NotDeterministicTransitionException;
import automaton.State;
import automaton.StateImpl;
import automaton.Transition;
import automaton.TransitionImpl;
import automaton.UnknownInitialStateException;

public class AutomatonInputFrame extends JFrame {
	private JLabel statesLabel = new JLabel("States: ");
	private JLabel initialStatesLabel = new JLabel("Initial states: ");
	private JLabel finalStatesLabel = new JLabel("Final states: ");
	private JLabel inputsLabel = new JLabel("Inputs: ");
	private JLabel transitionsLabel = new JLabel("Transitions: ");
	private JTextField states = new JTextField(15);
	private JTextField initialStates = new JTextField(15);
	private JTextField finalStates = new JTextField(15);
	private JTextField inputs = new JTextField(15);
	private JTextArea transitions = new JTextArea(5, 15);
	private JLabel remarkLabel = new JLabel("Put comma (,) between the different labels");
	private JButton ok = new JButton("OK");
	private JButton cancel = new JButton("Cancel");
	
	public AutomatonInputFrame(final GraphComponent component) {
		super("Automaton Information");
		setSize(300, 400);
		setResizable(false);
		setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
		JPanel panel = new JPanel();
		GridBagLayout gridbag = new GridBagLayout();
		panel.setLayout(gridbag);
		
		transitions.setLineWrap(true);
		transitions.setWrapStyleWord(true);
		JScrollPane scroll = new JScrollPane(transitions, 
				ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS, 
				ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER);
		ok.addActionListener(
				new ActionListener() {

					@Override
					public void actionPerformed(ActionEvent arg0) {
						// TODO Auto-generated method stub
						try {
							processDrawing(component);
						} catch (Exception e) {
							JOptionPane.showMessageDialog(null, e.getMessage());
							// TODO Auto-generated catch block
							e.printStackTrace();
						}
						dispose();
					}
					
				}
				);
		cancel.addActionListener(
				new ActionListener() {

					@Override
					public void actionPerformed(ActionEvent arg0) {
						// TODO Auto-generated method stub
						dispose();
					}
					
				}
				);
		addComponent(panel, gridbag, statesLabel, 0, 0, 1, 1, 10, 100, 
				GridBagConstraints.NONE, GridBagConstraints.EAST);
		addComponent(panel, gridbag, states, 1, 0, 9, 1, 90, 100, 
				GridBagConstraints.HORIZONTAL, GridBagConstraints.WEST);
		
		addComponent(panel, gridbag, initialStatesLabel, 0, 1, 1, 1, 10, 100, 
				GridBagConstraints.NONE, GridBagConstraints.EAST);
		addComponent(panel, gridbag, initialStates, 1, 1, 9, 1, 90, 100, 
				GridBagConstraints.HORIZONTAL, GridBagConstraints.WEST);

		addComponent(panel, gridbag, finalStatesLabel, 0, 2, 1, 1, 10, 100, 
				GridBagConstraints.NONE, GridBagConstraints.EAST);
		addComponent(panel, gridbag, finalStates, 1, 2, 9, 1, 90, 100, 
				GridBagConstraints.HORIZONTAL, GridBagConstraints.WEST);

		addComponent(panel, gridbag, inputsLabel, 0, 3, 1, 1, 10, 100, 
				GridBagConstraints.NONE, GridBagConstraints.EAST);
		addComponent(panel, gridbag, inputs, 1, 3, 9, 1, 90, 100, 
				GridBagConstraints.HORIZONTAL, GridBagConstraints.WEST);

		addComponent(panel, gridbag, transitionsLabel, 0, 4, 1, 1, 10, 100, 
				GridBagConstraints.NONE, GridBagConstraints.EAST);
		addComponent(panel, gridbag, scroll, 1, 4, 9, 1, 90, 100, 
				GridBagConstraints.HORIZONTAL, GridBagConstraints.WEST);
		
		addComponent(panel, gridbag, remarkLabel, 0, 5, 10, 1, 100, 100, 
				GridBagConstraints.NONE, GridBagConstraints.CENTER);
		
		addComponent(panel, gridbag, ok, 0, 6, 1, 1, 10, 100, 
				GridBagConstraints.NONE, GridBagConstraints.LINE_START);
		addComponent(panel, gridbag, cancel, 1, 6, 1, 1, 10, 100, 
				GridBagConstraints.NONE, GridBagConstraints.LINE_END);
		
		add(panel);
		setVisible(true);
	}
	
	private void addComponent(JPanel panel, GridBagLayout gridbag, Component component, 
			int gridx, int gridy, int gridwidth, int gridheight, 
			int weightx, int weighty, int fill, int anchor) {
		GridBagConstraints constraints = new GridBagConstraints();
		constraints.gridx = gridx;
		constraints.gridy = gridy;
		constraints.gridwidth = gridwidth;
		constraints.gridheight = gridheight;
		constraints.weightx = weightx;
		constraints.weighty = weighty; 
		constraints.fill = fill;
		constraints.anchor = anchor;
		gridbag.setConstraints(component, constraints);
		panel.add(component);
	}
	
	private List<String> cutString(String str, String delim) {
		List<String> result = new ArrayList<String>();
		StringTokenizer tokenizer = new StringTokenizer(str, delim);
		if (delim == null) {
			tokenizer = new StringTokenizer(str);
		}
		while (tokenizer.hasMoreTokens()) {
			result.add(tokenizer.nextToken());
		}
		
		return result;
	}
	
	private List<String> removeSpace(List<String> list) {
		List<String> result = new ArrayList<String>();
		for (String s : list) {
			String e = cutString(s, null).get(0);
			// Just take the first element b/c we don't care the remaining elements
			result.add(e);
		}
		return result;
	}
	
	public static ObservableAutomaton<String> convertInfoIntoAutomaton(AutomatonInfo info) 
	throws AutomatonInputException, NotDeterministicTransitionException, 
	NotDeterministicInitialStateException, UnknownInitialStateException {
		List<String> states = info.getStates();
		List<String> initialStates = info.getInitialStates();
		List<String> finalStates = info.getFinalStates();
		List<String> inputs = info.getInputs();
		List<String[]> transitions = info.getTransitions();
		
		if (states.isEmpty()) {
			throw new AutomatonInputException("The States list is empty");
		}
		if (initialStates.isEmpty()) {
			throw new AutomatonInputException("The Initial states list is empty");
		}
		if (finalStates.isEmpty()) {
			throw new AutomatonInputException("The Final states list is empty");
		}
		if (inputs.isEmpty()) {
			throw new AutomatonInputException("The Inputs list is empty");
		}
		if (transitions.isEmpty()) {
			throw new AutomatonInputException("The Transitions list is empty");
		}
		
		// Create an array of states
		State[] statesArray = new State[states.size()];
		for (int i = 0; i < statesArray.length; i++) {
			statesArray[i] = new StateImpl(false, false, states.get(i));
		}
		
		// Set initial state
		for (String s : initialStates) {
			int index = states.indexOf(s);
			if (index != -1) {
				((StateImpl) statesArray[index]).setInitial(true);
			} else {
				// Throw the exception here
				throw new AutomatonInputException("WRONG initial state: " + s);
			}
		}
		
		// Set final states
		for (String s : finalStates) {
			int index = states.indexOf(s);
			if (index != -1) {
				((StateImpl) statesArray[index]).setTerminal(true);
			} else {
				// Throw the exception here
				throw new AutomatonInputException("WRONG final state: " + s);
			}
		}
		
		// Create the transitions
		Transition<String>[] transitionsArray = new Transition[transitions.size()];
		for (int i = 0; i < transitionsArray.length; i++) {
			String[] str = transitions.get(i);
			int sourceIndex = states.indexOf(str[0]);
			int inputIndex = inputs.indexOf(str[1]);
			int targetIndex = states.indexOf(str[2]);
			if ((sourceIndex != -1) && (targetIndex != -1) && (inputIndex != -1)) {
				transitionsArray[i] = new TransitionImpl<String>(statesArray[sourceIndex], str[1], statesArray[targetIndex]);
			} else {
				// Throw the exception here
				throw new AutomatonInputException("WRONG transition: " + str);
			}
		}

		return new ObservableAutomaton<String>(transitionsArray);
	}
	
	private void processDrawing(GraphComponent component) throws AutomatonInputException, 
	NotDeterministicTransitionException, NotDeterministicInitialStateException, UnknownInitialStateException {
		List<String> list1 = removeSpace(cutString(states.getText(), ","));
		List<String> list2 = removeSpace(cutString(initialStates.getText(), ","));
		List<String> list3 = removeSpace(cutString(finalStates.getText(), ","));
		List<String> list4 = removeSpace(cutString(inputs.getText(), ","));
		List<String> list5 = removeSpace(cutString(transitions.getText(), "\n"));
		List<String[]> list6 = new ArrayList<String[]>();
		for (int i = 0; i < list5.size(); i++) {
			List<String> tmp = removeSpace(cutString(list5.get(i), ","));
			String[] ar = { tmp.get(0), tmp.get(1), tmp.get(2) };
			list6.add(ar);
		}
		AutomatonInfo info = new AutomatonInfo(list1, list2, list3, list4, list6);
		ObservableAutomaton<String> automaton = convertInfoIntoAutomaton(info);
		// Save this Automaton information for later use (Save to XML file)
		component.setInfo(info);
		/* 
		 * Create the necessary elements (vertices, edges, entryEdge, exitEdges)
		 * from the input automaton. These necessary elements will be used to
		 * draw this automaton on the component.
		 */
		component.convertAutomatonIntoGraph(automaton);
	}
}
